@hachej/boring-workspace 0.1.13 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +270 -42
- package/dist/CommandPalette-NOEOVkN2.js +5714 -0
- package/dist/{FileTree-BVfqs3rR.js → FileTree-Dl-qUAB0.js} +9 -9
- package/dist/MarkdownEditor-yc6mFsnI.js +533 -0
- package/dist/{WorkspaceLoadingState-BjZGQLS_.js → WorkspaceLoadingState-CSZfENWe.js} +145 -124
- package/dist/agent-tool-DEtfQPVB.d.ts +100 -0
- package/dist/app-front.d.ts +79 -67
- package/dist/app-front.js +253 -241
- package/dist/app-server.d.ts +17 -12
- package/dist/app-server.js +80 -10
- package/dist/{bootstrapServer-BRUqUpVW.d.ts → bootstrapServer-BreQ9QBc.d.ts} +8 -2
- package/dist/server.d.ts +10 -32
- package/dist/server.js +22 -127
- package/dist/shared.d.ts +1 -2
- package/dist/testing.d.ts +0 -63
- package/dist/testing.js +2248 -2401
- package/dist/workspace.css +1616 -974
- package/dist/workspace.d.ts +111 -450
- package/dist/workspace.js +417 -1635
- package/docs/INTERFACES.md +2 -2
- package/docs/PLUGIN_STRUCTURE.md +1 -1
- package/docs/plans/ASK_USER_QUESTIONS_PLUGIN_SPEC.md +131 -263
- package/docs/plans/GENERIC_EXPLORER_PLUGIN_PLAN.md +29 -27
- package/docs/plans/MACRO_PLUGIN_GENERIC_HELPERS_AUDIT.md +12 -12
- package/docs/plans/PANE_TO_AGENT_CHAT_ACTIONS_SPEC.md +366 -0
- package/docs/plans/README.md +2 -0
- package/docs/plans/archive/PLUGIN_MODEL.md +14 -14
- package/docs/plans/archive/SRC_FOLDER_REORG_PLAN.md +2 -3
- package/docs/plans/archive/WORKSPACE_V2_PLAN.md +1 -1
- package/package.json +3 -6
- package/dist/CommandPalette-Dme9em28.js +0 -5506
- package/dist/MarkdownEditor-CcCDF65H.js +0 -502
- package/dist/agent-tool-NvxKfist.d.ts +0 -28
- package/dist/explorer-DtLUnuah.d.ts +0 -129
package/README.md
CHANGED
|
@@ -1,49 +1,64 @@
|
|
|
1
|
-
# @boring
|
|
1
|
+
# @hachej/boring-workspace
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://www.npmjs.com/package/@hachej/boring-workspace)
|
|
7
|
+
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
Plugin system, panel registry, Dockview-based IDE layout, and typed agent-to-browser bridge for boring-ui apps. Everything the user touches — chat, files, catalogs, custom panes — composes here.
|
|
4
11
|
|
|
5
12
|
```bash
|
|
6
|
-
pnpm add @boring
|
|
13
|
+
pnpm add @hachej/boring-workspace
|
|
7
14
|
```
|
|
8
15
|
|
|
9
16
|
---
|
|
10
17
|
|
|
11
|
-
##
|
|
18
|
+
## TL;DR
|
|
19
|
+
|
|
20
|
+
**The Problem**: Agent apps need more than a chat window. You want file trees, editors, data tables, custom panes, keyboard shortcuts — but wiring all that chrome yourself means fighting layout engines, managing panel lifecycles, and building bridges between backend commands and frontend actions.
|
|
21
|
+
|
|
22
|
+
**The Solution**: `@hachej/boring-workspace` gives you a complete IDE-style workbench with a plugin system that contributes panes, tabs, commands, catalogs, surface resolvers, and React context. The agent backend talks to the UI through a typed pubsub bus. You write the domain logic; the workspace handles the chrome.
|
|
23
|
+
|
|
24
|
+
### Why Use @hachej/boring-workspace?
|
|
12
25
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
| Feature | What It Does |
|
|
27
|
+
|---------|--------------|
|
|
28
|
+
| **Plugin system** | Contribute panels, left-tabs, commands, catalogs, surface resolvers, and React bindings through a single manifest |
|
|
29
|
+
| **Dockview layout** | Split, resize, drag, and dock panels — VS Code–style behavior out of the box |
|
|
30
|
+
| **Auto code-splitting** | Lazy panels (`() => import(...)`) are automatically wrapped in `React.lazy + Suspense + ErrorBoundary` |
|
|
31
|
+
| **Typed UI bridge** | Agent calls `exec_ui({ kind: "openFile", params })` → panel opens in the workbench. SSE + HTTP fallback. |
|
|
32
|
+
| **Surface resolver** | Map agent-emitted `SurfaceOpenRequest` to panel opens — domain-specific routing without hardcoding panel IDs |
|
|
33
|
+
| **Built-in plugins** | File tree, editor, command palette, session management — ready on mount |
|
|
34
|
+
| **Composable** | Three levels: full layout (`IdeLayout`), provider + layout primitives, or headless hooks — pick what you need |
|
|
18
35
|
|
|
19
36
|
---
|
|
20
37
|
|
|
21
|
-
##
|
|
38
|
+
## Quick Example
|
|
22
39
|
|
|
23
40
|
```tsx
|
|
24
|
-
import { WorkspaceProvider, IdeLayout } from "@boring
|
|
25
|
-
import { ChatPanel } from "@boring
|
|
41
|
+
import { WorkspaceProvider, IdeLayout } from "@hachej/boring-workspace"
|
|
42
|
+
import { ChatPanel } from "@hachej/boring-agent"
|
|
26
43
|
|
|
27
|
-
|
|
44
|
+
// Full shell — plug in your chat and plugins
|
|
45
|
+
function App() {
|
|
28
46
|
return (
|
|
29
|
-
<WorkspaceProvider chatPanel={ChatPanel} plugins={[
|
|
47
|
+
<WorkspaceProvider chatPanel={ChatPanel} workspaceId="default" plugins={[myPanelPlugin, dataCatalogPlugin]}>
|
|
30
48
|
<IdeLayout />
|
|
31
49
|
</WorkspaceProvider>
|
|
32
50
|
)
|
|
33
51
|
}
|
|
34
52
|
```
|
|
35
53
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
## Writing a plugin
|
|
54
|
+
Add a panel in 8 lines:
|
|
39
55
|
|
|
40
56
|
```ts
|
|
41
|
-
import { defineFrontPlugin, definePanel } from "@boring
|
|
57
|
+
import { defineFrontPlugin, definePanel } from "@hachej/boring-workspace"
|
|
42
58
|
|
|
43
|
-
export const
|
|
44
|
-
id: "my-
|
|
45
|
-
label: "My
|
|
46
|
-
systemPrompt: "You can open widgets with the 'open-widget' tool.",
|
|
59
|
+
export const myPanelPlugin = defineFrontPlugin({
|
|
60
|
+
id: "my-panel",
|
|
61
|
+
label: "My Panel",
|
|
47
62
|
outputs: [
|
|
48
63
|
{
|
|
49
64
|
type: "panel",
|
|
@@ -58,37 +73,250 @@ export const myPlugin = defineFrontPlugin({
|
|
|
58
73
|
})
|
|
59
74
|
```
|
|
60
75
|
|
|
61
|
-
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Installation
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# pnpm
|
|
82
|
+
pnpm add @hachej/boring-workspace
|
|
83
|
+
|
|
84
|
+
# npm
|
|
85
|
+
npm install @hachej/boring-workspace
|
|
86
|
+
|
|
87
|
+
# from source
|
|
88
|
+
git clone https://github.com/hachej/boring-ui.git
|
|
89
|
+
cd boring-ui && pnpm install
|
|
90
|
+
pnpm --filter @hachej/boring-workspace build
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Architecture
|
|
96
|
+
|
|
97
|
+
### Plugin System
|
|
98
|
+
|
|
99
|
+
Plugins bootstrap into the workspace through a single `registerPlugin` call. `WorkspaceProvider` creates a `PanelRegistry`, calls `bootstrap()` with all plugins, and the registry auto-wraps lazy components.
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
WorkspaceProvider
|
|
103
|
+
├── bootstrap(plugins)
|
|
104
|
+
│ ├── registry.register(panel outputs)
|
|
105
|
+
│ ├── registry.register(left-tab outputs)
|
|
106
|
+
│ └── registry.register(command outputs)
|
|
107
|
+
│
|
|
108
|
+
├── DockviewShell
|
|
109
|
+
│ ├── registry.getComponents() → lazy-wrapped panels
|
|
110
|
+
│ └── DockviewReact (layout chrome)
|
|
111
|
+
│
|
|
112
|
+
├── UiBridgeClient
|
|
113
|
+
│ ├── SSE command stream
|
|
114
|
+
│ └── HTTP poll fallback
|
|
115
|
+
│
|
|
116
|
+
└── Layout (IdeLayout / ChatLayout / ResponsiveDockviewShell)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### UI Bridge
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
Agent Backend Frontend
|
|
123
|
+
────────────── ────────
|
|
124
|
+
POST /api/v1/ui/commands ──► UiBridge dispatches
|
|
125
|
+
{ kind, params } │ ├── openFile
|
|
126
|
+
│ ├── openPanel
|
|
127
|
+
│ └── showNotification
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Agent plugins emit `UiCommand` values on the server. The workbench has a typed event bus that dispatches them to the right handler.
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
POST /api/v1/ui/commands
|
|
134
|
+
{ kind: "openFile", params: { path: "src/index.ts" } }
|
|
135
|
+
|
|
136
|
+
↓
|
|
137
|
+
|
|
138
|
+
UiBridgeClient receives → dispatches to FileTree plugin → expands node + focuses editor
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Built-in Plugins
|
|
142
|
+
|
|
143
|
+
| Plugin | What It Adds |
|
|
144
|
+
|--------|-------------|
|
|
145
|
+
| Filesystem plugin | File tree (left tab), editor (center panel), file navigation |
|
|
146
|
+
| Chat plugin | Integrates the injected `ChatPanel` into the layout |
|
|
147
|
+
| Command palette | `⌘K`-driven search across commands, panels, and surfaces |
|
|
148
|
+
| Session plugin | Lightweight session toolbar (current session, dropdown, new chat) |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Package Surfaces
|
|
153
|
+
|
|
154
|
+
| Import | Environment | What You Get |
|
|
155
|
+
|--------|-------------|--------------|
|
|
156
|
+
| `@hachej/boring-workspace` | Browser | `WorkspaceProvider`, `IdeLayout`, `defineFrontPlugin`, `definePanel`, all built-ins |
|
|
157
|
+
| `@hachej/boring-workspace/server` | Node | `defineServerPlugin()`, server routes, UI tools, Pi package helpers |
|
|
158
|
+
| `@hachej/boring-workspace/shared` | Any | `PaneProps`, `SurfaceOpenRequest`, `UiCommand`, plugin types |
|
|
159
|
+
| `@hachej/boring-workspace/events` | Any | Typed event bus for bridge communication |
|
|
160
|
+
| `@hachej/boring-workspace/charts` | Browser | Recharts wrappers for data visualization |
|
|
161
|
+
| `@hachej/boring-workspace/testing` | Browser | Test utilities and mock providers |
|
|
162
|
+
| `@hachej/boring-workspace/app/front` | Browser | App composition: `WorkspaceAgentFront` |
|
|
163
|
+
| `@hachej/boring-workspace/app/server` | Node | App composition: `createWorkspaceAgentApp` |
|
|
164
|
+
| `@hachej/boring-workspace/globals.css` | Browser | Global CSS for the workspace chrome |
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Plugin Output Types
|
|
169
|
+
|
|
170
|
+
| Output Type | Contributed Surface | Example |
|
|
171
|
+
|-------------|--------------------|---------|
|
|
172
|
+
| `panel` | Center/right/bottom pane | Code editor, data table, settings page |
|
|
173
|
+
| `left-tab` | Persistent sidebar tab | File tree, data catalog, status panel |
|
|
174
|
+
| `command` | Command palette entry | "Toggle dark mode", "Format files" |
|
|
175
|
+
| `catalog` | Searchable data explorer | Customer list with faceted filters |
|
|
176
|
+
| `surface-resolver` | Maps `exec_ui` → panel | `openFile` → editor panel with path |
|
|
177
|
+
| `binding` | React context in provider tree | Theme, auth, workspace-scoped state |
|
|
178
|
+
| `provider` | Binding + `apiBaseUrl` injection | Server-side plugin config passed to front |
|
|
179
|
+
| `agent-tool` | New agent tool via server plugin | `deploy`, `test`, `lint` commands |
|
|
180
|
+
|
|
181
|
+
### Writing a Plugin
|
|
62
182
|
|
|
63
183
|
```ts
|
|
64
|
-
import
|
|
184
|
+
import { defineFrontPlugin, definePanel } from "@hachej/boring-workspace"
|
|
185
|
+
import type { PaneProps } from "@hachej/boring-workspace"
|
|
186
|
+
|
|
187
|
+
export const statusPlugin = defineFrontPlugin({
|
|
188
|
+
id: "build-status",
|
|
189
|
+
label: "Build Status",
|
|
190
|
+
outputs: [
|
|
191
|
+
{
|
|
192
|
+
type: "left-tab",
|
|
193
|
+
panel: definePanel({
|
|
194
|
+
id: "build-status-tab",
|
|
195
|
+
title: "Build",
|
|
196
|
+
placement: "left",
|
|
197
|
+
component: () => import("./StatusTab").then(m => ({ default: m.StatusTab })),
|
|
198
|
+
}),
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
type: "panel",
|
|
202
|
+
panel: definePanel({
|
|
203
|
+
id: "build-details",
|
|
204
|
+
title: "Build Details",
|
|
205
|
+
placement: "bottom",
|
|
206
|
+
component: () => import("./BuildDetails").then(m => ({ default: m.BuildDetails })),
|
|
207
|
+
}),
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
})
|
|
65
211
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
//
|
|
212
|
+
// Panel components receive PaneProps<T>:
|
|
213
|
+
function StatusTab({ params, api, containerApi }: PaneProps<{}>) {
|
|
214
|
+
// params — data when panel is opened
|
|
215
|
+
// api — DockviewPanelApi (close, setTitle, …)
|
|
216
|
+
// containerApi — DockviewApi (addPanel, layout, …)
|
|
69
217
|
}
|
|
70
218
|
```
|
|
71
219
|
|
|
72
|
-
|
|
220
|
+
### Server Plugins
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { defineServerPlugin } from "@hachej/boring-workspace/server"
|
|
224
|
+
|
|
225
|
+
export const statusServerPlugin = defineServerPlugin({
|
|
226
|
+
id: "build-status",
|
|
227
|
+
tools: [buildTool], // Agent tools
|
|
228
|
+
routes: [statusRoutes], // Fastify routes
|
|
229
|
+
provisioning: [seedBuildDir], // Environment setup
|
|
230
|
+
})
|
|
231
|
+
```
|
|
73
232
|
|
|
74
233
|
---
|
|
75
234
|
|
|
76
|
-
##
|
|
235
|
+
## Configuration
|
|
236
|
+
|
|
237
|
+
### WorkspaceProvider Props
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
<WorkspaceProvider
|
|
241
|
+
chatPanel={ChatPanel} // Required: React component for chat
|
|
242
|
+
workspaceId={id} // Required: workspace identifier
|
|
243
|
+
plugins={[pluginA, pluginB]} // Optional: extend with custom plugins
|
|
244
|
+
apiBaseUrl="http://localhost:3000" // Optional: backend URL
|
|
245
|
+
authHeaders={...} // Optional: auth for HTTP requests
|
|
246
|
+
layoutPreferences={...} // Optional: initial Dockview layout JSON
|
|
247
|
+
/>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## How @hachej/boring-workspace Compares
|
|
253
|
+
|
|
254
|
+
| Feature | @hachej/boring-workspace | VS Code (Theia) | Custom layout |
|
|
255
|
+
|---------|--------------------------|-----------------|---------------|
|
|
256
|
+
| Panel layout | ✅ Dockview, drag/drop/split | ✅ Tabbed | ⚠️ Build yourself |
|
|
257
|
+
| Plugin system | ✅ Panels + tabs + commands + catalogs | ✅ Extension API | ❌ DIY |
|
|
258
|
+
| Agent bridge | ✅ Typed UiBridge pubsub | ❌ Not agent-native | ❌ DIY |
|
|
259
|
+
| Code splitting | ✅ Auto-detects lazy factories | ⚠️ Require-based | ⚠️ Manual |
|
|
260
|
+
| Setup time | ✅ ~10 lines | ❌ Heavy framework | ❌ Weeks |
|
|
261
|
+
|
|
262
|
+
**When to use @hachej/boring-workspace:**
|
|
263
|
+
- Building an agent-powered IDE with customizable panes
|
|
264
|
+
- You need plugins to add panels without touching the shell
|
|
265
|
+
- You want the agent to open things (files, data, charts) in the workbench
|
|
266
|
+
|
|
267
|
+
**When it might not fit:**
|
|
268
|
+
- You just want a chat box (use `@hachej/boring-agent` standalone)
|
|
269
|
+
- You need a full VS Code replacement (no language server protocol support)
|
|
270
|
+
- You want to control every pixel of the layout (use Dockview directly)
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Troubleshooting
|
|
275
|
+
|
|
276
|
+
| Error | Cause | Fix |
|
|
277
|
+
|-------|-------|-----|
|
|
278
|
+
| `Panel not found: <id>` | Plugin not registered | Check `plugins` prop on `WorkspaceProvider` |
|
|
279
|
+
| Blank panel / white screen | Lazy component threw | Check `PluginErrorBoundary` — inspect console |
|
|
280
|
+
| `UiBridge not connected` | Backend not running | Verify `apiBaseUrl` and backend endpoint |
|
|
281
|
+
| Commands not arriving | SSE blocked by proxy | Use `?poll=true` on `/api/v1/ui/commands/next` |
|
|
282
|
+
| Plugin not loading | Missing `defineFrontPlugin` | All plugins must be wrapped in `defineFrontPlugin` |
|
|
283
|
+
| Duplicate panel IDs | Two plugins register same ID | Rename one panel's `id` field |
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Limitations
|
|
288
|
+
|
|
289
|
+
- **Plugin panels share the same registry** — name collisions between plugins cause the last-registered panel to win. Namespace your IDs.
|
|
290
|
+
- **No code editor language server** — The editor ships CodeMirror6 with syntax highlighting but no LSP. Semantic features (go-to-definition, rename) are not available.
|
|
291
|
+
- **Rich text editor is TipTap-based** — It's included but opt-in via peer dependencies. Not all TipTap extensions are wired up.
|
|
292
|
+
- **Frontend code must not value-import `@hachej/boring-agent`** — Package-neutral workspace code stays agent-free. Use the `chatPanel` injection pattern instead.
|
|
293
|
+
- **Layout state persistence is the shell's job** — Workspace doesn't save/restore layouts between sessions. The shell owns `layoutPreferences`.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## FAQ
|
|
298
|
+
|
|
299
|
+
**Q: What's the difference between a `panel` and a `left-tab`?**
|
|
300
|
+
A: `panel` opens in the main Dockview area (center/right/bottom). `left-tab` is a persistent sidebar element that stays docked to the left. Users open panels programmatically; left-tabs are always visible.
|
|
301
|
+
|
|
302
|
+
**Q: How do I lazy-load a panel?**
|
|
303
|
+
A: Pass a zero-arg arrow function `() => import("./Pane").then(m => ({ default: m.Pane }))`. The registry auto-detects this pattern and wraps it in `React.lazy + Suspense`. Don't set `lazy: true`.
|
|
304
|
+
|
|
305
|
+
**Q: Can the agent open my plugin's panel?**
|
|
306
|
+
A: Yes. Register a `surface-resolver` that maps the agent's `SurfaceOpenRequest` to your panel ID. Then the agent can use `exec_ui` to open it.
|
|
307
|
+
|
|
308
|
+
**Q: Why can't frontend workspace code import from `@hachej/boring-agent`?**
|
|
309
|
+
A: The workspace package stays package-neutral so it can be used without the agent. The `chatPanel` prop injects the agent's UI, keeping the dependency inverted.
|
|
310
|
+
|
|
311
|
+
**Q: What's `surface-resolver` for?**
|
|
312
|
+
A: It decouples agent-side "open X" requests from frontend panel IDs. The agent says `{ kind: "open-series", seriesId: "GDPC1" }`, and the surface resolver maps that to `{ panelId: "series-chart", params: { seriesId: "GDPC1" } }`.
|
|
313
|
+
|
|
314
|
+
---
|
|
77
315
|
|
|
78
|
-
|
|
79
|
-
|---|---|
|
|
80
|
-
| `panel` | a center/right/bottom pane |
|
|
81
|
-
| `left-tab` | a persistent sidebar tab |
|
|
82
|
-
| `command` | a command palette entry |
|
|
83
|
-
| `catalog` | a searchable data explorer |
|
|
84
|
-
| `surface-resolver` | maps agent `exec_ui` calls to panel opens |
|
|
316
|
+
*About Contributions:* Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via `gh` and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.
|
|
85
317
|
|
|
86
318
|
---
|
|
87
319
|
|
|
88
|
-
##
|
|
320
|
+
## License
|
|
89
321
|
|
|
90
|
-
|
|
91
|
-
|---|---|
|
|
92
|
-
| `@boring/core` | DB, auth, app factory |
|
|
93
|
-
| `@boring/workspace` | Plugin system, layouts |
|
|
94
|
-
| `@boring/agent` | Agent runtime + tools |
|
|
322
|
+
MIT
|